package com.googlecode.horncomb.orm.ibatis;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.googlecode.horncomb.bean.filter.ConditionSet;
import com.googlecode.horncomb.bean.id.generator.IdGenerator;
import com.googlecode.horncomb.bean.id.generator.JdkUUIDGenerator;
import com.googlecode.horncomb.bean.order.Order;
import com.googlecode.horncomb.bean.paged.CollectionWarpper;
import com.googlecode.horncomb.bean.paged.DefaultPagedList;
import com.googlecode.horncomb.bean.paged.LimitPagedList;
import com.googlecode.horncomb.bean.paged.SimpleCollectionWrapper;
import com.googlecode.horncomb.orm.id.EntityIdUtil;

@SuppressWarnings("unchecked")
public abstract class AbstractCommonSqlMapDao extends SqlSessionDaoSupport {
	private boolean useLimitQuery = true;

	/**
	 * id generator, default is
	 * com.googlecode.horncomb.orm.id.impl.JdkUUIDGeneratorImpl
	 */
	protected IdGenerator<?> idGenerator = new JdkUUIDGenerator();
	/** id field name */
	protected String idName = "id"; //$NON-NLS-1$

	/** the name of get statement */
	protected String getStatementName = "get"; //$NON-NLS-1$
	/** the name of insert statement */
	protected String insertStatementName = "insert"; //$NON-NLS-1$
	/** the name of update statement */
	protected String updateStatementName = "update"; //$NON-NLS-1$
	/** the name of delete statement */
	protected String deleteStatementName = "delete"; //$NON-NLS-1$
	/** the name of query statement */
	protected String queryStatementName = "query"; //$NON-NLS-1$
	/** the name of query count statement */
	protected String queryCountStatementName = "queryCount"; //$NON-NLS-1$

	private String conditionParameterName = "conditionSet"; //$NON-NLS-1$
	private String entityObjectParameterName = "entity"; //$NON-NLS-1$
	private String queryOrderParameterName = "queryOrder"; //$NON-NLS-1$
	private String pagedStartParameterName = "pagedStart"; //$NON-NLS-1$
	private String pagedEndParameterName = "pagedEnd"; //$NON-NLS-1$
	private String pagedSizeParameterName = "pagedSize"; //$NON-NLS-1$

	// -------------------------------------------------------------
	/**
	 * @param constraints
	 * @return
	 */
	protected Map buildParam(ConditionSet constraints) {
		return buildParam(constraints, null);
	}

	/**
	 * @param constraints
	 * @param order
	 * @return
	 */
	protected Map buildParam(ConditionSet constraints, Order order) {
		Map param = new HashMap();
		if (constraints != null) {
			param.put(this.conditionParameterName, constraints
					.getConstraintsMap());
		}
		if (order != null) {
			param.put(this.queryOrderParameterName, order.getTokens());
		}
		return param;
	}

	/**
	 * @param constraints
	 * @param order
	 * @param startIndex
	 * @param endIndex
	 * @return
	 */
	protected Map buildParam(ConditionSet constraints, Order order,
			int startIndex, int endIndex) {
		Map param = buildParam(constraints, order);
		param.put(this.pagedStartParameterName, startIndex);
		param.put(this.pagedEndParameterName, endIndex);
		param.put(this.pagedSizeParameterName, endIndex - startIndex + 1);
		return param;
	}

	/**
	 * build statement name
	 * @param statementId
	 * @return statement name
	 */
	protected String buildStatementName(String namespace, String simpleStmtName) {
		Assert.hasText(namespace);
		return namespace + "." + simpleStmtName;
	}

	protected String getIdName(Class entityType) {
		Assert.notNull(entityType);
		return EntityIdUtil.getIdNameFromCache(entityType);
	}

	protected Serializable getIdValue(Object entity) {
		try {
			return EntityIdUtil.getIdValue(entity, this.getIdName(entity
					.getClass()));
		} catch (Exception ex) {
			throw new ObjectRetrievalFailureException(entity.getClass(), ex);
		}
	}

	protected void setIdValue(Object entity, Serializable idValue) {
		try {
			EntityIdUtil.setIdValue(entity, idValue, this.getIdName(entity
					.getClass()));
		} catch (DataAccessException dae) {
			try {
				// do nothing
			} catch (Exception ex) {
				this.logger.warn("cannot set id as [null]", ex);
			}
			throw dae;
		} catch (Exception ex) {
			this.logger.error("cannot set new id", ex);
			throw new Error(ex);
		}
	}

	// -------------------------------------------------------------
	// get

	protected Object doGet(ConditionSet cons, String stmtName, String namespace)
			throws DataAccessException {
		stmtName = this.buildStatementName(namespace, StringUtils
				.hasText(stmtName) ? stmtName : this.getStatementName);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("call sqlmap statement [" + stmtName + "]");
		}
		Assert.notNull(cons);
		Assert.notEmpty(cons.getConstraintsMap());
		return this.getSqlSession().selectOne(stmtName, this.buildParam(cons));
	}

	protected int doDelete(ConditionSet cons, String stmtName, String namespace)
			throws DataAccessException {
		stmtName = this.buildStatementName(namespace, StringUtils
				.hasText(stmtName) ? stmtName : this.deleteStatementName);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("call sqlmap statement [" + stmtName + "]");
		}
		Assert.notNull(cons);
		Assert.notEmpty(cons.getConstraintsMap());
		return this.getSqlSession().delete(stmtName, this.buildParam(cons));
	}

	public Serializable createIdValue(Object entity) throws DataAccessException {
		Assert.notNull(this.idGenerator, "id gennerator cannot be null.");
		Serializable idValue = this.idGenerator.generateId(entity);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("create id value: " + idValue + "");
		}
		return idValue;
	}

	/**
	 * if the entity is new to save
	 * @param entity
	 * @return
	 */
	protected boolean isNew(Object entity) {
		Assert.notNull(entity);
		Serializable id = this.getIdValue(entity);
		if (id == null || !StringUtils.hasText(id.toString())
				|| "0".equals(id.toString())) {
			return true;
		} else {
			return false;
		}
	}

	protected int doInsert(Object entity, String stmtName, String namespace)
			throws DataAccessException {
		stmtName = this.buildStatementName(namespace, StringUtils
				.hasText(stmtName) ? stmtName : this.insertStatementName);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("call sqlmap statement [" + stmtName + "]");
		}
		Assert.notNull(entity);
		EntityIdUtil.initIdLocationCacheIfNeed(entity.getClass(), this.idName);
		if (EntityIdUtil.hasIdInCache(entity.getClass())) {
			Serializable idValue = this.getIdValue(entity);
			if (this.idGenerator != null
					&& (idValue == null || !StringUtils.hasText(idValue
							.toString()))) {
				idValue = this.createIdValue(entity);
				Assert.notNull(idValue);
				this.setIdValue(entity, idValue);
			}
		}

		return this.getSqlSession().insert(stmtName, entity);
	}

	protected int doUpdate(ConditionSet cons, Object entity, String stmtName,
			String namespace) throws DataAccessException {
		stmtName = this.buildStatementName(namespace, StringUtils
				.hasText(stmtName) ? stmtName : this.updateStatementName);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("call sqlmap statement [" + stmtName + "]");
		}
		Assert.isTrue(entity != null || (cons != null && !cons.isEmpty()));
		// build parameter
		Map param = null;
		if (cons == null || cons.isEmpty()) {
			param = new HashMap();
		} else {
			param = this.buildParam(cons);
		}
		if (StringUtils.hasText(this.entityObjectParameterName)) {
			param.put(this.entityObjectParameterName, entity);
		} else {
			param.putAll(SqlMapEntityDaoUtil.convertObjectToMap(entity));
		}
		return this.getSqlSession().update(stmtName, param);
	}

	protected int doQueryCount(ConditionSet constraints, String qryCntStmtName,
			String namespace) throws DataAccessException {
		qryCntStmtName = this.buildStatementName(namespace, StringUtils
				.hasText(qryCntStmtName) ? qryCntStmtName
				: this.queryCountStatementName);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("call sqlmap statement [" + qryCntStmtName + "]");
		}
		return (Integer) this.getSqlSession().selectOne(qryCntStmtName,
				this.buildParam(constraints));
	}

	protected CollectionWarpper doQuery(ConditionSet constraints, Order order,
			int page, int pageSize, String qryStmtName, String qryCntStmtName,
			String namespace) throws DataAccessException {
		Map qryParam = null;
		CollectionWarpper clcWp;
		if (page > 0 && pageSize > 0) {
			// need paged
			if (this.useLimitQuery) {
				int cnt = this.doQueryCount(constraints, qryCntStmtName,
						namespace);
				LimitPagedList lpl = new LimitPagedList(page, pageSize, cnt);

				// not find any record
				if (cnt == 0) {
					// return empty LimitPagedList
					lpl.setSource(new ArrayList(0));
					return (CollectionWarpper) lpl;
				} else {
					qryParam = this.buildParam(constraints, order, lpl
							.getFirstElementOnPage(), lpl
							.getLastElementOnPage());
				}

				clcWp = lpl;
			} else {
				DefaultPagedList pl = new DefaultPagedList(page, pageSize);
				qryParam = this.buildParam(constraints, order, pl
						.getFirstElementOnPage(), pl.getLastElementOnPage());
				clcWp = pl;
			}
		} else {
			// no paged
			clcWp = new SimpleCollectionWrapper();
			qryParam = this.buildParam(constraints, order);
		}
		qryStmtName = this.buildStatementName(namespace, StringUtils
				.hasText(qryStmtName) ? qryStmtName : this.queryStatementName);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("call sqlmap statement [" + qryStmtName + "]");
		}
		List result = this.getSqlSession().selectList(qryStmtName, qryParam);
		clcWp.setSource(result);
		return clcWp;
	}

	/**
	 * @param useLimitQuery
	 *            the useLimitQuery to set
	 */
	public void setUseLimitQuery(boolean useLimitQuery) {
		this.useLimitQuery = useLimitQuery;
	}

	// -------------------------------------------------------------
	/**
	 * set id generator
	 * @param idGenerator
	 *            the idGenerator to set
	 * @see com.googlecode.horncomb.bean.id.generator.IdGenerator
	 */
	public void setIdGenerator(IdGenerator idGenerator) {
		this.idGenerator = idGenerator;
	}

	/**
	 * @param idName
	 *            the idName to set
	 */
	public void setIdName(String idName) {
		Assert.hasText(idName);
		this.idName = idName;
	}

	// ----------------------------------------------------------------------
	/**
	 * @param getStatementName
	 *            the getStatementName to set
	 */
	public void setGetStatementName(String name) {
		Assert.hasText(name);
		this.getStatementName = name;
	}

	/**
	 * @param insertStatementName
	 *            the insertStatementName to set
	 */
	public void setInsertStatementName(String name) {
		Assert.hasText(name);
		this.insertStatementName = name;
	}

	/**
	 * @param updateStatementName
	 *            the updateStatementName to set
	 */
	public void setUpdateStatementName(String name) {
		Assert.hasText(name);
		this.updateStatementName = name;
	}

	/**
	 * @param deleteStatementName
	 *            the deleteStatementName to set
	 */
	public void setDeleteStatementName(String name) {
		Assert.hasText(name);
		this.deleteStatementName = name;
	}

	/**
	 * @param queryStatementName
	 *            the queryStatementName to set
	 */
	public void setQueryStatementName(String name) {
		Assert.hasText(name);
		this.queryStatementName = name;
	}

	/**
	 * @param queryCountStatementName
	 *            the queryCountStatementName to set
	 */
	public void setQueryCountStatementName(String name) {
		Assert.hasText(name);
		this.queryCountStatementName = name;
	}

	// -------------------------------------------------------------
	/**
	 * @param conditionParameterName
	 *            the conditionParameterName to set
	 */
	public void setConditionParameterName(String conditionParameterName) {
		Assert.hasText(conditionParameterName);
		this.conditionParameterName = conditionParameterName;
	}

	/**
	 * @param queryOrderParameterName
	 *            the queryOrderParameterName to set
	 */
	public void setQueryOrderParameterName(String queryOrderParameterName) {
		Assert.hasText(queryOrderParameterName);
		this.queryOrderParameterName = queryOrderParameterName;
	}

	/**
	 * @param pagedStartParameterName
	 *            the pagedStartParameterName to set
	 */
	public void setPagedStartParameterName(String pagedStartParameterName) {
		Assert.hasText(pagedStartParameterName);
		this.pagedStartParameterName = pagedStartParameterName;
	}

	/**
	 * @param pagedEndParameterName
	 *            the pagedEndParameterName to set
	 */
	public void setPagedEndParameterName(String pagedEndParameterName) {
		Assert.hasText(pagedStartParameterName);
		this.pagedEndParameterName = pagedEndParameterName;
	}

	/**
	 * @param entityObjectParameterName
	 *            the entityObjectParameterName to set
	 */
	public void setEntityObjectParameterName(String entityObjectParameterName) {
		// Assert.hasText(entityObjectParameterName);
		this.entityObjectParameterName = entityObjectParameterName;
	}
}
